package org.goodCompany.sound.encoders;

import com.sun.media.ui.AudioFormatChooser;

import javax.media.*;
import javax.media.control.TrackControl;
import javax.media.datasink.DataSinkEvent;
import javax.media.datasink.DataSinkListener;
import javax.media.datasink.EndOfStreamEvent;
import javax.media.format.AudioFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author trollswagen
 *         Date: Jan 6, 2007
 *         Time: 2:43:16 PM
 */
public class Mp3Encoder2 {
    private static final Log log = LogFactory.getLog(Mp3Encoder2.class);

    private String sourceFileName;
    private String targetFileName;

    /*
    * Public Getter/Setter Methods
    */
    public String getSourceFileName() {
        return sourceFileName;
    }

    public void setSourceFileName(String sourceFileName) {
        this.sourceFileName = sourceFileName;
    }

    public String getTargetFileName() {
        return targetFileName;
    }

    public void setTargetFileName(String targetFileName) {
        this.targetFileName = targetFileName;
    }

    /*
    * Public Methods
    */
    public void encode() throws Exception {
        MediaLocator src = new MediaLocator("file:" + getSourceFileName());
        DataSource dataSource = Manager.createDataSource(src);
        Processor processor = Manager.createProcessor(dataSource);
        processor.addControllerListener(new ProcessorControllerListener());

        waitForState(processor, Processor.Configured);
        updateProcessorFormat(processor);
        waitForState(processor, Processor.Realized);

        DataSource outputSouce = processor.getDataOutput();

        MediaLocator dest = new MediaLocator("file:" + getTargetFileName());

        DataSink sink = Manager.createDataSink(outputSouce, dest);
        sink.addDataSinkListener(new SinkListener(sink));
        sink.open();
        sink.start();
        
        processor.start();
    }


    /*
    * Private Methods
    */
    private void updateProcessorFormat(Processor processor) {
        ContentDescriptor[] contentDescriptors = processor.getSupportedContentDescriptors();
        TrackControl[] trackControls = processor.getTrackControls();

        TrackControl trackControl = trackControls[0];
        Format format = trackControl.getFormat();
        AudioFormatChooser panelAudio = new AudioFormatChooser(trackControl.getSupportedFormats(), (AudioFormat) format);
        panelAudio.setTrackEnabled(trackControl.isEnabled());

        if (!panelAudio.isTrackEnabled()) {
            trackControl.setEnabled(false);
        }

        processor.setContentDescriptor(contentDescriptors[3]);

        trackControl.setEnabled(true);
        trackControl.setFormat(format);
    }

    private synchronized void waitForState(Processor processor, int state) {
        StateListener sl = new StateListener(processor, state);
        processor.addControllerListener(sl);

        if (state == Processor.Configured) {
            processor.configure();
        } else if (state == Processor.Realized) {
            processor.realize();
        }

        sl.waiting();
        processor.removeControllerListener(sl);
    }

    /*
    * Private Classes
    */
    private class StateListener implements ControllerListener {
        private final Object stateLock = new Object();
        private boolean failedState = false;

        private final Processor processor;
        private final int state;

        public StateListener(Processor processor, int state) {
            this.processor = processor;
            this.state = state;
        }

        public void controllerUpdate(ControllerEvent ce) {
            if (ce instanceof ControllerClosedEvent) {
                this.failedState = true;
            }
            if (ce != null) {
                synchronized (this.stateLock) {
                    this.stateLock.notifyAll();
                }
            }
        }

        public void waiting() {
            while (processor.getState() < state && !this.failedState) {
                synchronized (this.stateLock) {
                    try {
                        this.stateLock.wait();
                    }
                    catch (InterruptedException ie) {
                        this.failedState = true;
                    }
                }
            }
        }
    }

    private class SinkListener implements DataSinkListener {
        private final DataSink dataSink;

        public SinkListener(DataSink dataSink) {
            this.dataSink = dataSink;
        }

        public void dataSinkUpdate(DataSinkEvent dataSinkEvent) {
            log.info("dataSinkUpdate(" + dataSinkEvent + ")");
            if (dataSinkEvent instanceof EndOfStreamEvent) {
                this.dataSink.close();
                System.exit(333);
            }
        }
    }

    private class ProcessorControllerListener implements ControllerListener {
        public void controllerUpdate(ControllerEvent event) {
            log.info("controllerUpdate(" + event + ")");
            if (event instanceof EndOfMediaEvent) {
                System.exit(222);
            }
        }
    }
}
